import os
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from threading import Lock, Semaphore, Thread
from typing import Dict, Optional

import requests
import schedule
from flask import Flask
from loguru import logger

from core.config import settings
from core.db.crud import (
    create_lottery_table,
    get_last_bet_no,
    get_lottery_table_name,
    insert_lottery_data,
    update_last_bet_no,
)
from core.db.utils import sanitize_table_name
from core.db_conn import get_db_connection

app = Flask(__name__)

REQUEST_LOCK = Lock()
MAX_WORKERS = min(settings.THREAD_MAX_WORKERS, (os.cpu_count() or 1) + 4)
REQUEST_SEMAPHORE = Semaphore(60)


class DataCollector:

    def __init__(self):
        self.active_lottery_codes = [
            "Ethereum1",
            "TRON1",
            "Binance1",
            "TRON1-Hash",
            "Binance-BTC",
        ]
        self.lottery_info_cache = {}  # 彩种信息缓存

    @property
    def conn(self):
        return get_db_connection()

    def concurrent_collect(self):
        logger.info("开始并发采集任务")
        executor = min(settings.THREAD_MAX_WORKERS, (os.cpu_count() or 1) + 4)
        start_time = time.time()

        with ThreadPoolExecutor(max_workers=executor) as executor:
            futures = {
                executor.submit(self.fetch_and_save, code): code
                for code in self.active_lottery_codes
            }
            for future in as_completed(futures):
                code = futures[future]
                try:
                    success = future.result()
                    logger.info(f"彩种 {code} 采集{'成功' if success else '失败'}")
                except Exception as e:
                    logger.error(f"彩种 {code} 采集出错: {e}")
        elapsed = time.time() - start_time
        logger.info(f"并发采集完成，耗时 {elapsed:.2f} 秒")

    def fetch_and_save(self, lottery_code: str):
        data = self.fetch_lottery_data(lottery_code)
        if not data:
            logger.warning(f"未获取到 {lottery_code} 的数据，跳过保存")
            return False
        saved_count = self._save_data_to_db(data)
        return saved_count > 0


    def fetch_lottery_data(self, lottery_code: str) -> Optional[Dict]:
        with REQUEST_SEMAPHORE:
            try:
                url = f"{settings.BASE_URL}/data?code={lottery_code}&limit=5&key={settings.API_KEY}&sort=desc"
                logger.debug(f"请求API: {url.split('key=')[0]}...")

                response = requests.get(url, timeout=10)
                response.raise_for_status()
                data = response.json()

                if data.get("info") != "success" or not data.get("data"):
                    logger.error(f"API返回错误: {data.get('info')}")
                    return None

                data["data"].sort(
                    key=lambda x: datetime.strptime(x["openTime"], "%Y-%m-%d %H:%M:%S"),
                    reverse=True,
                )

                self.lottery_info_cache[lottery_code] = {
                    "lotteryName": data.get("lotteryName", ""),
                    "lotteryCode": lottery_code,
                }
                return data
            except requests.RequestException as e:
                logger.error(f"采集 {lottery_code} 失败: {e}")
                return None
            except Exception as e:
                logger.error(f"采集 {lottery_code} 失败: {e}")
                return None

    def _ensure_table_exists(self, lottery_code: str, lottery_name: str) -> str | None:
        table_name = sanitize_table_name(lottery_code)
        try:
            exists_table = get_lottery_table_name(self.conn, table_name)
            if not exists_table:
                create_lottery_table(self.conn, table_name)
                logger.info(f"创建表 {table_name} 用于存储 {lottery_name} 数据")
            return table_name
        except Exception as e:
            logger.error(f"确保表 {table_name} 存在时出错: {e}")
        finally:
            self.conn.close()

    def _save_data_to_db(self, data: Dict) -> int:
        lottery_code = data["lotteryCode"]
        lottery_name = data["lotteryName"]

        table_name = self._ensure_table_exists(lottery_code, lottery_name)

        try:
            last_bet_no = get_last_bet_no(self.conn, lottery_code)
            saved_count = 0
            fetch_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

            conn = get_db_connection()
            with conn.cursor() as cursor:

                api_latest_bet_no = None
                if data["data"]:
                    api_latest_bet_no = max(
                        int(item["gmtBetNo"]) for item in data["data"] if item.get("gmtBetNo", "").isdigit()
                    )
                if last_bet_no is None or (api_latest_bet_no is not None and int(api_latest_bet_no) > int(last_bet_no)):
                    new_data = []
                    for item in data["data"]:
                        current_bet_no = item.get("gmtBetNo", "")
                        if not current_bet_no:
                            continue
                        if last_bet_no is None or int(current_bet_no) > int(last_bet_no):
                            new_data.append(item)
                    if not new_data:
                        logger.info(f"没有新数据需要保存 for {lottery_code}")
                        return 0
                    new_data.sort(key=lambda x: datetime.strptime(x["openTime"], "%Y-%m-%d %H:%M:%S"))
                    max_bet_no = None
                    for item in new_data:
                        clean_open_number = item.get("openNumber", "").replace(",", "")
                        rowcount = insert_lottery_data(
                            cursor,
                            table_name,
                            item.get("gmtBetNo", ""),
                            clean_open_number,
                            item.get("openTime", ""),
                            fetch_time,
                        )
                        if rowcount == 1:
                            saved_count += 1
                            logger.info(
                                f"新增号码 - 彩种: {lottery_name}({lottery_code}), "
                                f"期号: {item.get('gmtBetNo', '')}, "
                                f"号码: {clean_open_number}, "
                                f"开奖时间: {item.get('openTime', '')}"
                            )
                            current_bet_no = int(item.get("gmtBetNo", "0"))
                            if max_bet_no is None or current_bet_no > max_bet_no:
                                max_bet_no = current_bet_no
                    self.conn.commit()
                    if saved_count > 0:
                        logger.info(f"保存 {saved_count} 条新数据到 {lottery_code} 表")
                        if max_bet_no is not None:
                            update_last_bet_no(self.conn, lottery_code, str(max_bet_no))
                    return saved_count
                else:
                    logger.info(f"{lottery_code} 没有新数据需要保存")
                    return 0
        except Exception as e:
            logger.error(f"保存 {lottery_code} 数据时出错: {e}")
        finally:
            self.conn.close()
        return 0


collector = DataCollector()


def scheduled_job():
    """
    每分钟执行一次的全量采集
    :return:
    """
    try:
        collector.concurrent_collect()
    except Exception as e:
        logger.error(f"定时任务异常: {e}")


def incremental_check():
    """
    每秒执行一次的增量检查
    :return:
    """
    try:
        for code in collector.active_lottery_codes:
            try:
                collector.fetch_and_save(code)
            except Exception as e:
                logger.error(f"增量检查 {code} 出错: {e}")
    except Exception as e:
        logger.error(f"增量检查异常: {e}")


def run_scheduler():
    """
    运行调度任务
    :return:
    """
    schedule.every(1).minutes.do(scheduled_job)
    schedule.every(1).seconds.do(incremental_check)

    while True:
        schedule.run_pending()
        time.sleep(0.1)


def run():
    from core.db.initialization import init_db
    from core.log_cfg import set_log

    # set_log()
    init_db()
    Thread(target=collector.concurrent_collect).start()
    Thread(target=run_scheduler, daemon=True).start()
    app.run(host="0.0.0.0", port=9998, threaded=True)
